---- 异步报告器
local reporter = {}

local json = require "cjson"
local Const = require("skywalking.constants")
local tracer = require("skywalking.tracer")
local http = require("resty.http")
local ngx = ngx
local SEGMENT_BATCH_COUNT = 100
local metadata_shdict = ngx.shared.tracing_buffer
local tostring = tostring

function reporter.new(_logger, backend_http_uri, filter_url_list)
    if not backend_http_uri then
        return
    end

    local filter_map
    if filter_url_list and next(filter_url_list) then
        filter_map = table.new(0, 10)
        for _, uri in pairs(filter_url_list) do
            if string.sub(uri, -1, -1) == '/' then
                uri = string.sub(uri, 1, -2)
            end
            filter_map[uri] = ''
        end
    end
    return setmetatable({logger = _logger, backend_http_uri = backend_http_uri, timerDelay = 3, filter_map = filter_map}, {__index = reporter})
end

-- 启动
function reporter:boot(...)
    self:startBackendTimer()
end

function reporter:send(reporter_spans)
    if not reporter_spans then
        ngx.log(ngx.ERR, "reporter_spans is null")
        return
    end

    local filter_map = self.filter_map
    if filter_map and reporter_spans and reporter_spans.spans then
        for _, span in pairs(reporter_spans.spans) do
            local operationName = span.operationName
            if operationName then
                local uri = ngx.var.uri
                local cap = ngx.re.match(uri, [[(.*)/[^/]*$]])
                if cap and cap[1] then
                    if filter_map[cap[1]] then
                        span.operationName = cap[1]
                    end
                end
            end
        end
    end
    local segmentJson, err = json.encode(reporter_spans)

    if not segmentJson then
        ngx.log(ngx.ERR, "failed to encode segment: " .. tostring(err))
        return
    end

    -- self.logger.info("http_reporter_segmentJson = " .. tostring(segmentJson))

    local length, err = metadata_shdict:lpush(Const.segment_queue, segmentJson)
    if not length then
        ngx.log(ngx.ERR, "failed to push segment: " .. tostring(err))
        return
    end
end

function reporter:SetBackendTimerDelay(delay)
    self.timerDelay = delay
end

function reporter:startBackendTimer()
    local new_timer = ngx.timer.every
    local check

    local log = ngx.log
    local ERR = ngx.ERR

    check = function()
        self:reportTraces()
    end

    -- just one worder consumer the data
    if 0 == ngx.worker.id() then
        local ok, err = new_timer(self.timerDelay, check)
        if not ok then
            log(ERR, "failed to create timer: ", err)
            return
        end
    end
end

--- report trace segments to the backend
function reporter:reportTraces()
    local log = ngx.log
    local DEBUG = ngx.DEBUG

    local queue = metadata_shdict
    local segment = queue:rpop(Const.segment_queue)
    local segmentTransform = ""

    local count = 0
    local totalCount = 0

    while segment ~= nil do
        if #segmentTransform > 0 then
            segmentTransform = segmentTransform .. ","
        end

        segmentTransform = segmentTransform .. segment
        segment = queue:rpop(Const.segment_queue)
        count = count + 1

        if count >= SEGMENT_BATCH_COUNT then
            if self:http_send("[" .. segmentTransform .. "]") then
                totalCount = totalCount + count
            end

            segmentTransform = ""
            count = 0
        end
    end

    if #segmentTransform > 0 then
        if self:http_send("[" .. segmentTransform .. "]") then
            totalCount = totalCount + count
        end
    end
end

function reporter:http_send(segment_data)
    local log = ngx.log
    local ERR = ngx.ERR

    local httpc = http.new()

    -- log(ERR, segment_data)
    local res, err =
        httpc:request_uri(
        self.backend_http_uri .. "/v3/segments",
        {
            method = "POST",
            body = segment_data,
            headers = {
                ["Content-Type"] = "application/json"
            }
        }
    )

    if err == nil then
        if res.status ~= 200 then
            log(ERR, "Segment report fails, response code ", res.status)
            return false
        end
    else
        log(ERR, "Segment report fails, ", err)
        return false
    end

    return true
end

return reporter
